package com.by.toolkitspring.support;

import com.by.toolkit.model.ESPageResp;
import com.by.toolkit.model.PageResp;
import com.by.toolkit.utils.ByIfTrueUtil;
import com.by.toolkit.utils.ExceptionUtil;
import com.by.toolkitspring.config.AsyncExecutorConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.dromara.dynamictp.core.DtpRegistry;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;

@Slf4j
public class AssembleDataSupport {

    public static final AssembleDataSupport INSTANCE = new AssembleDataSupport();

    private AssembleDataSupport() {
    }

    public static <T> PageResp<T> assembleData(PageResp<T> pageResp, Consumer<List<T>> consumer) {
        return ByIfTrueUtil.getIfTrueOrElse(Objects.nonNull(pageResp) && CollectionUtils.isNotEmpty(pageResp.getDatas())
                , () -> {
                    List<T> datas = pageResp.getDatas();
                    consumer.accept(datas);
                    return PageResp.of(pageResp.getTotalSize(), datas);
                }, PageResp::ofNull);
    }

    public static <T> PageResp<T> assembleDataAsync(PageResp<T> pageResp, Collection<Consumer<List<T>>> consumers) {
        return assembleDataAsync(pageResp, consumers, 1, TimeUnit.SECONDS, e -> {
            log.warn("[PageRespSupport][doPageRespAsync] assembleDataAsyncError hasException:[{}] ", ExceptionUtil.getStackTrace(e));
        });
    }

    public static <T> PageResp<T> assembleDataAsync(PageResp<T> pageResp, Collection<Consumer<List<T>>> consumers, Consumer<Throwable> errorCallback) {
        return assembleDataAsync(pageResp, consumers, 1, TimeUnit.SECONDS, errorCallback);
    }

    public static <T> PageResp<T> assembleDataAsync(PageResp<T> pageResp, Collection<Consumer<List<T>>> consumers, long timeout, TimeUnit unit, Consumer<Throwable> errorCallback) {
        return ByIfTrueUtil.getIfTrueOrElse(Objects.nonNull(pageResp) && CollectionUtils.isNotEmpty(pageResp.getDatas())
                , () -> {
                    List<T> datas = pageResp.getDatas();
                    assembleDataAsync(datas, consumers, timeout, unit, errorCallback);
                    return PageResp.of(pageResp.getTotalSize(), datas);
                }, PageResp::ofNull);
    }


    public static <T> ESPageResp<T> assembleData(ESPageResp<T> pageResp, Consumer<List<T>> consumer) {
        return ByIfTrueUtil.getIfTrueOrElse(Objects.nonNull(pageResp) && CollectionUtils.isNotEmpty(pageResp.getDatas())
                , () -> {
                    List<T> datas = pageResp.getDatas();
                    consumer.accept(datas);
                    return ESPageResp.of(pageResp.getTotalSize(), pageResp.getPreSearchAfter(), pageResp.getCurrSearchAfter(), pageResp.getNextSearchAfter(), datas);
                }, ESPageResp::ofNull);
    }

    public static <T> ESPageResp<T> assembleData(ESPageResp<T> pageResp, Collection<Consumer<List<T>>> consumers) {
        return ByIfTrueUtil.getIfTrueOrElse(Objects.nonNull(pageResp) && CollectionUtils.isNotEmpty(pageResp.getDatas())
                , () -> {
                    List<T> datas = pageResp.getDatas();
                    consumers.forEach(c -> {
                        try {
                            c.accept(datas);
                        } catch (Exception e) {
                            log.warn("[AssembleDataSupport][assembleData] hasException:[{}] ", ExceptionUtil.getStackTrace(e));
                        }
                    });
                    return ESPageResp.of(pageResp.getTotalSize(), pageResp.getPreSearchAfter(), pageResp.getCurrSearchAfter(), pageResp.getNextSearchAfter(), datas);
                }, ESPageResp::ofNull);
    }

    public static <T> ESPageResp<T> assembleDataAsync(ESPageResp<T> pageResp, Collection<Consumer<List<T>>> consumers) {
        return assembleDataAsync(pageResp, consumers, 1, TimeUnit.SECONDS, e -> {
            log.warn("[PageRespSupport][doPageRespAsync] assembleDataAsyncError hasException:[{}] ", ExceptionUtil.getStackTrace(e));
        });
    }

    public static <T> ESPageResp<T> assembleDataAsync(ESPageResp<T> pageResp, Collection<Consumer<List<T>>> consumers, Consumer<Throwable> errorCallback) {
        return assembleDataAsync(pageResp, consumers, 1, TimeUnit.SECONDS, errorCallback);
    }

    public static <T> ESPageResp<T> assembleDataAsync(ESPageResp<T> pageResp, Collection<Consumer<List<T>>> consumers, long timeout, TimeUnit unit, Consumer<Throwable> errorCallback) {
        return ByIfTrueUtil.getIfTrueOrElse(Objects.nonNull(pageResp) && CollectionUtils.isNotEmpty(pageResp.getDatas())
                , () -> {
                    List<T> datas = pageResp.getDatas();
                    assembleDataAsync(datas, consumers, timeout, unit, errorCallback);
                    return ESPageResp.of(pageResp.getTotalSize(), pageResp.getPreSearchAfter(), pageResp.getCurrSearchAfter(), pageResp.getNextSearchAfter(), datas);
                }, ESPageResp::ofNull);
    }


    public static <T> List<T> assembleDataAsync(List<T> datas, Collection<Consumer<List<T>>> consumers, Consumer<Throwable> errorCallback) {
        return assembleDataAsync(datas, consumers, 1, TimeUnit.SECONDS, errorCallback);
    }

    public static <T> List<T> assembleDataAsync(List<T> datas, Collection<Consumer<List<T>>> consumers, long timeout, TimeUnit unit, Consumer<Throwable> errorCallback) {
        return ByIfTrueUtil.getIfTrueOrElse(CollectionUtils.isNotEmpty(datas)
                , () -> {
                    try {
                        CompletableFuture.allOf(consumers.stream()
                                .map(c -> CompletableFuture.runAsync(() -> c.accept(datas)
                                        , DtpRegistry.getDtpExecutor(AsyncExecutorConfig.BY_ASYNC_EXECUTOR_POOL_NAME)))
                                .toArray(CompletableFuture[]::new)
                        ).get(timeout, unit);
                    } catch (InterruptedException | ExecutionException | TimeoutException e) {
                        errorCallback.accept(e);
                    }
                    return datas;
                }
                , Collections::emptyList);
    }

    public static <T> List<T> assembleData(List<T> datas, Collection<Consumer<List<T>>> consumers, Consumer<Throwable> errorCallback) {
        return ByIfTrueUtil.getIfTrueOrElse(CollectionUtils.isNotEmpty(datas)
                , () -> {
                    try {
                        consumers.forEach(c -> c.accept(datas));
                    } catch (Exception e) {
                        errorCallback.accept(e);
                    }
                    return datas;
                }
                , Collections::emptyList);
    }

}
